/*================================================================
*   Copyright (C) 2024 baichao All rights reserved.
*
*   文件名称：pipe_dup2_std.c
*   创 建 者：baichao
*   创建日期：2024年04月06日
*   描    述：模拟shell管道通信。核心原理就是利用dup2系统调用复制文件描述符。
             简单来说，就是第一个子进程的标准输出被绑定到了管道的写入端，于是第一个命令的输出，写入了管道。
             而第二个子进程管道将其标准输入绑定到管道的读取端，只要管道里面有了内容，这些内容就成了标准输入。
*
================================================================*/

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>

#define READ_END 0
#define WRITE_END 1

int main() {
  int pipefd[2];
  pid_t pid1, pid2;

  // 创建管道
  if (pipe(pipefd) < 0) {
    perror("pipe");
    exit(EXIT_FAILURE);
  }

  // 创建第一个子进程
  pid1 = fork();
  if (pid1 < 0) {
    perror("fork");
    exit(EXIT_FAILURE);
  } else if (pid1 == 0) {  // 子进程1
    // 关闭管道的读端
    close(pipefd[READ_END]);

    // 将标准输出重定向到管道的写端
    dup2(pipefd[WRITE_END], STDOUT_FILENO);

    // 关闭管道的旧的写端
    close(pipefd[WRITE_END]);

    // 子进程1写入数据到标准输出（现在关联到管道的写端）
    printf("子进程1写入的数据\n");
    exit(EXIT_SUCCESS);
  }

  // 创建第二个子进程
  pid2 = fork();
  if (pid2 < 0) {
    perror("fork");
    exit(EXIT_FAILURE);
  } else if (pid2 == 0) {  // 子进程2
    // 关闭管道的写端
    close(pipefd[WRITE_END]);

    // 将标准输入重定向到管道的读端
    dup2(pipefd[READ_END], STDIN_FILENO);

    // 关闭管道的旧的读端
    close(pipefd[READ_END]);

    // 子进程2从标准输入（现在关联到管道的读端）读取数据
    for (int i = 0; i < 10; i++) {
      char buffer[1024];
      ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
      if (bytes_read > 0) {
        buffer[bytes_read] = '\0';  // 确保字符串以null字符结尾
        printf("子进程2读取到的数据: %s\n", buffer);
        break;
      }
      printf("bytes_read:%d,error:%s\n\n", bytes_read, strerror(errno));
      sleep(1);
    }

    exit(EXIT_SUCCESS);
  }

  // 父进程关闭管道两端
  close(pipefd[READ_END]);
  close(pipefd[WRITE_END]);

  // 等待子进程结束
  wait(NULL);
  wait(NULL);

  return 0;
}
